package qq2564874169.tuaotuao.fx.utils;


import qq2564874169.tuaotuao.fx.Action1T;
import qq2564874169.tuaotuao.fx.FxStream;
import qq2564874169.tuaotuao.fx.io.StringReader;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

public class HttpUtil {
    private static final String LINE = "\r\n";

    public static String upload(String url, Map<String, String> param, List<HttpFile> files) {
        String boundary = "---------------------------" + System.currentTimeMillis();
        HttpURLConnection connection = rawPost(url, null, conn -> {
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            try {
                OutputStream out = new DataOutputStream(conn.getOutputStream());
                if (param != null && !param.isEmpty()) {
                    StringBuilder buf = new StringBuilder();
                    for (String key : param.keySet()) {
                        buf.setLength(0);
                        buf.append(LINE).append("--").append(boundary).append(LINE);
                        buf.append("Content-Disposition: form-data; name=\"").append(key).append("\"");
                        buf.append(LINE).append(LINE);
                        buf.append(IfUtil.firstNotNull(param.get(key), ""));
                    }
                    out.write(buf.toString().getBytes());
                }
                if (files != null && !files.isEmpty()) {
                    StringBuilder buf = new StringBuilder();
                    for (HttpFile file : files) {
                        buf.setLength(0);
                        buf.append(LINE).append("--").append(boundary).append(LINE);
                        buf.append("Content-Disposition: form-data; name=\"").append(file.paramName).append("\"; filename=\"").append(file.fileName).append("\"");
                        buf.append(LINE);
                        buf.append("Content-Type:").append(file.mimeType());
                        buf.append(LINE).append(LINE);
                        out.write(buf.toString().getBytes());
                        try (DataInputStream in = new DataInputStream(file.fileStream)) {
                            int len;
                            byte[] data = new byte[4096];
                            while ((len = in.read(data)) != -1) {
                                out.write(data, 0, len);
                            }
                        }
                    }
                }
                out.write((LINE + "--" + boundary + "--" + LINE).getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                throw new RuntimeException("写入失败", e);
            }
        });
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            } else {
                throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
            }
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    public static String post(String url, String body, Action1T<HttpURLConnection> before) {
        HttpURLConnection connection = rawPost(url, body, before);
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            }
            throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    public static String post(String url, String body) {
        return post(url, body, null);
    }

    public static String post(String url, Map<String, Object> body, Action1T<HttpURLConnection> before) {
        return post(url, UrlUtil.join(body, false), before);
    }

    public static String post(String url, Map<String, Object> body) {
        return post(url, body, null);
    }

    public static String get(String url, Action1T<HttpURLConnection> before) {
        HttpURLConnection connection = rawGet(url, before);
        try {
            if (connection.getResponseCode() == 200) {
                return StringReader.readToEnd(connection.getInputStream());
            }
            throw new ResponseException(connection.getResponseCode(), connection.getResponseMessage());
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    public static String get(String url) {
        return get(url, null);
    }

    public static HttpURLConnection rawPost(String url, String body, Action1T<HttpURLConnection> before) {
        return httpTo("POST", url, body, before);
    }

    public static HttpURLConnection rawGet(String url, Action1T<HttpURLConnection> before) {
        return httpTo("GET", url, null, before);
    }

    public static HttpURLConnection rawGet(String url) {
        return rawGet(url, null);
    }

    private static HttpURLConnection httpTo(String method, String url, String body, Action1T<HttpURLConnection> before) {
        try {
            URLConnection conn = new URL(url).openConnection();
            if (conn instanceof HttpsURLConnection https) {
                https.setSSLSocketFactory(new MySSLSocketFactory());
                https.setHostnameVerifier(new TrustAnyHostnameVerifier());
            }
            HttpURLConnection connection = (HttpURLConnection) conn;
            connection.setInstanceFollowRedirects(true);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
            if (body != null) {
                connection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
                connection.setRequestProperty("content-length", body.length() + "");
                if (before != null) {
                    before.execute(connection);
                }
                try (OutputStream stream = connection.getOutputStream();
                     PrintWriter writer = new PrintWriter(stream)) {
                    writer.write(body);
                    writer.flush();
                }
            } else if (before != null) {
                before.execute(connection);
            }
            return connection;
        } catch (Exception e) {
            throw ExUtil.toRuntime(e);
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private static class MySSLSocketFactory extends SSLSocketFactory {
        private static String[] cipherSuites;

        private static SSLContext sslContext;

        static {
            try {
                Security.setProperty("jdk.tls.disabledAlgorithms", "SSLv3, DH keySize < 768");
                String[] ciphers = SSLContext.getDefault().getSocketFactory().getSupportedCipherSuites();
                List<String> list = FxStream.create(ciphers).toList();
                list.add("SSL_RSA_WITH_RC4_128_SHA");
                cipherSuites = list.toArray(new String[0]);

                sslContext = SSLContext.getInstance("TLSv1.2");
                sslContext.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
            } catch (Exception e) {
                throw ExUtil.toRuntime(e);
            }
        }

        // 设置算法套
        private void setSSLParams(SSLSocket sslSocket) {
            sslSocket.setUseClientMode(true);
            sslSocket.setEnabledCipherSuites(cipherSuites);
        }

        private static SSLContext getSslContext() {
            return sslContext;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return cipherSuites;
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return cipherSuites;
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean b) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(socket, host, port, b);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port, localHost, localPort);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(host, port);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            SSLSocket sslSocket = (SSLSocket) getSslContext().getSocketFactory().createSocket(address, port, localAddress, localPort);
            setSSLParams(sslSocket);
            return sslSocket;
        }

        private static class DefaultTrustManager implements X509TrustManager {

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }
    }

    public static class HttpFile {
        public String paramName;
        public String fileName;
        public String contentType;
        public InputStream fileStream;

        public String mimeType() {
            if (StringUtil.hasValue(contentType)) {
                return contentType;
            }
            String ext = FileUtil.getExt(fileName).toLowerCase();
            return switch (ext) {
                case ".txt", ".csv", ".json", ".log" -> "text/plain";
                case ".bmp" -> "image/x-ms-bmp";
                case ".jpg", ".jpeg" -> "image/jpeg";
                case ".gif" -> "image/gif";
                case ".png" -> "image/png";
                case ".mp3" -> "audio/mpeg";
                case ".mid" -> "audio/midi";
                case ".ogg" -> "audio/ogg";
                case ".mp4a" -> "audio/mp4";
                case ".wav" -> "audio/wav";
                case ".wma" -> "audio/x-ms-wma";
                case ".avi" -> "video/x-msvideo";
                case ".dv" -> "video/x-dv";
                case ".mp4" -> "video/mp4";
                case ".mpeg", ".mpg" -> "video/mpeg";
                case ".mov" -> "video/quicktime";
                case ".mkv" -> "video/x-matroska";
                case ".zip" -> "application/zip";
                case ".rar" -> "application/x-rar";
                case ".7z" -> "application/x-7z-compressed";
                case ".doc" -> "application/vnd.ms-word";
                case ".docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                case ".xls" -> "application/vnd.ms-excel";
                case ".xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                case ".ppt" -> "application/vnd.ms-powerpoint";
                case ".pptx" -> "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                case ".pdf" -> "application/pdf";
                case ".xml" -> "application/xml";
                default -> throw new RuntimeException("无法识别的后缀格式：" + ext);
            };
        }

        public static HttpFile setNew(Action1T<HttpFile> action) {
            HttpFile file = new HttpFile();
            action.execute(file);
            return file;
        }
    }

    public static class ResponseException extends RuntimeException {
        private int code;

        public int getResponseCode() {
            return code;
        }

        public ResponseException(int code, String message) {
            super(message == null ? "response code: " + code : message);
            this.code = code;
        }
    }
}
